﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Client {
	class ServiceClient {
		Uri uri;
		ServiceModelServerClient service;
		Boolean connected = false;
		private static volatile ServiceClient instance;
		protected static Object syncRoot = new Object();

		private ServiceClient() {
			setUri("localhost", 8000);
		}

		public static ServiceClient Instance {
			get {
				if (instance == null) {
					lock (syncRoot) {
						if (instance == null)
							instance = new ServiceClient();
					}
				}
				return instance;
			}
		}

		private void setUri(String host, Decimal port) {
			uri = new Uri(Uri.UriSchemeNetTcp + "://" + host + ":" + port + "/wifiperf/");

			if (!host.Equals(uri.Host)) {
				throw new Exception("Host name is wrong");
			}
		}

		private void setUri(String host) {
			setUri(host, uri.Port);
		}

		private void setUri(Decimal port) {
			setUri(uri.Host, port);
		}

		public Uri GetUri() {
			return uri;
		}

		public void SetHost(String host) {
			setUri(host);
			if (IsConnected()) {
				Reconnect();
			}
		}

		public void SetPort(Decimal port) {
			setUri(port);
			if (IsConnected()) {
				Reconnect();
			}
		}

		public void Reconnect() {
			Disconnect();
			Connect();
		}

		public ServiceModelServerClient Connect() {
			if (connected) {
				if (null != service && (service.State == CommunicationState.Opened || service.State == CommunicationState.Opening)) {
					return service;
				} else {
					Disconnect();
				}
			}

			Uri address = GetUri("ServiceModelServer");

			// Construct InstanceContext to handle messages on callback interface.
			InstanceContext instanceContext = new InstanceContext(new ServiceModelServerCallback());

			// Create a client.
			NetTcpBinding binding = new NetTcpBinding();
			binding.CloseTimeout = TimeSpan.FromSeconds(10);
			binding.OpenTimeout = TimeSpan.FromSeconds(30);
			binding.ReceiveTimeout = TimeSpan.FromSeconds(120);
			binding.SendTimeout = TimeSpan.FromSeconds(120);

			binding.ReliableSession.InactivityTimeout = TimeSpan.FromHours(12);
			binding.ReliableSession.Ordered = true;

			binding.Security.Mode = SecurityMode.Message;
			binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

			EndpointAddress endpoint = new EndpointAddress(address);

			service = new ServiceModelServerClient(instanceContext, binding, endpoint);

			service.ClientCredentials.Windows.ClientCredential.UserName = "wifiperf";
			service.ClientCredentials.Windows.ClientCredential.Password = "wifiperf";

			Logger.Info("Connected to server");

			Logger.Debug("Testing Connection");
			if (TestConnection() && service.State == CommunicationState.Opened) {
				Logger.Debug("Connection OK");
				Logger.Log("Client guid: " + service.get_Guid());
			} else {
				Logger.Warning("Connection failed!");
				Disconnect();
				return null;
			}

			IsConnected();
			return service;
		}

		public Uri GetUri(String serviceName) {
			return new Uri(GetUri(), serviceName);
		}

		public Boolean TestConnection() {
			try {
				return service.TestConnection();
			} catch (TimeoutException exception) {
				Logger.Error("TimeoutException: " + exception.Message);
				return false;
			} catch (FaultException exception) {
				Logger.Error("FaultException: " + exception.Message);
				return false;
			} catch (ArgumentException exception) {
				Logger.Error(exception.Message);
				return false;
			} catch (EndpointNotFoundException exception) {
				Logger.Error(exception.Message);
				if (null != exception.InnerException) {
					Logger.Debug(exception.InnerException.Message);
				}
				return false;
			} catch (CommunicationException exception) {
				Logger.Error("CommunicationException: " + exception.Message);
				return false;
			}
		}

		public void Disconnect() {
			connected = false;

			if (null != service && (service.State == CommunicationState.Opened || service.State == CommunicationState.Opening) && service.State != CommunicationState.Closing) {
				try {
					service.Disconnect();
					service.Close();
				} catch (ObjectDisposedException) {
					service.Abort();
					Logger.Critical("Disconnecting ObjectDisposedException raised");
				} catch (CommunicationException e) {
					service.Abort();
					Logger.Critical("Disconnecting CommunicationException: " + e.Message);
				} catch (TimeoutException) {
					service.Abort();
					Logger.Warning("Timeout Exception during disconnecting");
				}
				service = null;
			}
		}

		public ServiceModelServerClient GetService() {
			return service;
		}

		~ServiceClient() {
			Disconnect();
		}

		public Guid Name {
			get {
				return service.get_Guid();
			}
		}

		public Boolean IsConnected() {
			if (null == service) {
				return false;
			}

			if (service.State == CommunicationState.Faulted) {
				Disconnect();
			}

			// jeśli faulted, to teoretycznie połączony, trzeba rozłączyć
			return (connected = (service.State == CommunicationState.Opened));
		}
	}
}
